#include <stdlib.h>

#include "../hle.h"

#include "../fs/metafs.h"
#include "__IoFileMgrForUser.h"
#include "__ThreadManForUser.h"

#define O_RDONLY    0x0001
#define O_WRONLY    0x0002
#define O_RDWR      0x0003
#define O_NBLOCK    0x0010
#define O_APPEND    0x0100
#define O_CREAT     0x0200
#define O_TRUNC     0x0400
#define O_NOWAIT    0x8000

typedef struct
{
    SceIoStat d_stat;
    char d_name[256];
    u32 d_private;
} SceIoDirEnt;

void sceIoAssign()
{
    char *aliasname = mem_readStr(PARAM(0));
    char *physname = mem_readStr(PARAM(1));
    char *devname = mem_readStr(PARAM(2));
    u32 flag = PARAM(3);
    _log(INF, HLE, "sceIoAssign(%s, %s, %s, %08x, ...)", aliasname, physname, devname, flag);
    RETURN(0);
    mem_freeStr(aliasname);
    mem_freeStr(physname);
    mem_freeStr(devname);
}

void sceIoGetstat()
{
    char *filename = mem_readStr(PARAM(0));
    u32 addr = PARAM(1);
    SceIoStat stat;
    FileInfo info = mfs_getFileInfo(filename);

    mem_readStruct(addr, &stat, sizeof(SceIoStat));
    _log(INF, HLE, "sceIoGetstat(%s, %08x)", filename, addr);
    
    __IoGetStat(&stat, info);
    mem_writeStruct(addr, &stat, sizeof(SceIoStat));

    RETURN(0);
    mem_freeStr(filename);
}

void sceIoRead()   /* (s32 fd, void *data, s32 size); */
{
    SceUID id = PARAM(0);
    FileNode *f;
    if (id == 3)
    {
        _log(INF, HLE, "sceIoRead STDIN");
        RETURN(0); /* stdin */
        return;
    }

    f = ko_get(id);
    if (f)
    {
        if (PARAM(1))
        {
            u32 size = PARAM(2);
            f->asyncResult = RETURN(mfs_readFileToAddr(f->handle, PARAM(1), size));
            _log(INF, HLE, "%d = sceIoRead(%d, 0x%08x, 0x%08x)", f->asyncResult, id, PARAM(1), size);
        }
        else {
            _log(ERR, HLE, "sceIoRead Reading into zero pointer");
            RETURN(-1);
        }
    }
    else {
        _log(ERR, HLE, "sceIoRead(%d, 0x%08x, 0x%08x): bad file descriptor", id, PARAM(1), PARAM(2));
        RETURN(SCE_ERROR_BAD_FILE_DESCRIPTOR);
    }
}

void sceIoReadAsync()
{
    SceUID id = PARAM(0);
    FileNode *f;
    if (id == 3)
    {  
        _log(INF, HLE, "sceIoReadAsync STDIN");
        RETURN(0); /* stdin */
        return;
    }
   
    f = ko_get(id);
    if (f)
    {  
        if (PARAM(1))
        {  
            u32 size = PARAM(2);
            f->asyncResult = mfs_readFileToAddr(f->handle, PARAM(1), size);
            _log(INF, HLE, "0 = sceIoReadAsync(%d, 0x%08x, 0x%08x)", f->asyncResult, id, PARAM(1), size);
            RETURN(0);
        }
        else {
            _log(ERR, HLE, "sceIoReadAsync Reading into zero pointer");
            RETURN(-1);
        }
    }
    else {
        _log(ERR, HLE, "sceIoReadAsync(%d, 0x%08x, 0x%08x): bad file descriptor", id, PARAM(1), PARAM(2));
        RETURN(SCE_ERROR_BAD_FILE_DESCRIPTOR);
    }
}

void sceIoWrite()  /* (s32 fd, void *data, s32 size);  */
{
    SceUID id = PARAM(0);
    s32 size = PARAM(2);
    FileNode *f;
    if (id == 2)
    {
        /* stderr! */
        char *str = mem_readStr(PARAM(1));
        _log(INF, HLE, "stderr [%08x]: %s", PARAM(1), str);
        RETURN(size);
        mem_freeStr(str);
        return;
    }
    if (id == 1)
    {
        /* stdout! */
        char *str = mem_readStr(PARAM(1));
        char temp = str[size];
        str[size] = 0;
        _log(INF, HLE, "stdout [%08x]: %s", PARAM(1), str);
        str[size] = temp;
        RETURN(size);
        mem_freeStr(str);
        return;
    }

    f = ko_get(id);
    if (f) {
        _log(INF, HLE, "sceIoWrite(fd = %d, ptr = %08x, size = %08x)", id, PARAM(1), size);
        f->asyncResult = RETURN(mfs_writeFileFromAddr(f->handle, PARAM(1), size));
    }
    else {
        _log(ERR, HLE, "sceIoWrite(fd = %d, ptr = %08x, size = %08x): bad file descriptor", id, PARAM(1), size);
        RETURN(SCE_ERROR_BAD_FILE_DESCRIPTOR);
    }
}

void sceIoLseek() /* (s32 fd, s32 offset, s32 whence);  */
{
    SceUID id = PARAM(0);
    FileNode *f = ko_get(id);
    if (f)
    {
        u64 offset = PARAM(2) | ((u64)PARAM(3) << 32);
        s32 whence = PARAM(4);
        u32 ret;
        FileMove seek;
        
        switch (whence)
        {
        case 0:
            seek = FILEMOVE_BEGIN;
            break;
        case 1:
            seek = FILEMOVE_CURRENT;
            break;
        case 2:
            seek = FILEMOVE_END;
            break;
        default:
            _log(ERR, HLE, "Unknown whence !");
            RETURN(-1);
            return;
        }

        ret = mfs_seekFile(f->handle, offset, seek);
        _log(INF, HLE, "%08x = sceIoLseek(id = %d, offset = %016x, whence = %d)", ret, id, offset, whence);
        RETURN(ret);
        RETURN1(0);
    }
    else {
        _log(ERR, HLE, "sceIoLseek(id = %d, offset = %016x, whence = %d): bad file descriptor", id, PARAM(2) | ((u64)PARAM(3) << 32), PARAM(4));
        RETURN(SCE_ERROR_BAD_FILE_DESCRIPTOR);
    }
}

void sceIoLseek32()
{
    SceUID id = PARAM(0);
    FileNode *f = ko_get(id);
    u32 offset = PARAM(1);
    if (f)
    {
        s32 whence = PARAM(2);
        FileMove seek;
        _log(INF, HLE, "sceIoLseek32(id = %d, offset = %08x, whence = %d)", id, offset, whence);
        
        switch (whence)
        {
        case 0:
            seek = FILEMOVE_BEGIN;
            break;
        case 1:
            seek = FILEMOVE_CURRENT;
            break;
        case 2:
            seek = FILEMOVE_END;
            break;
        default:
            _log(ERR, HLE, "Unknown whence !");
            RETURN(-1);
            return;
        }

        RETURN(mfs_seekFile(f->handle, offset, seek));
    }
    else {
        _log(ERR, HLE, "sceIoLseek32(id = %d, offset = %08x, whence = %d): bad file descriptor", id, PARAM(2) | ((u64)PARAM(3) << 32), PARAM(4));
        RETURN(SCE_ERROR_BAD_FILE_DESCRIPTOR);
    }
}

void sceIoOpen()
{
    char *filename = mem_readStr(PARAM(0));
    s32 mode = PARAM(1);
    SceUID id;
    FileNode *f;
    u32 h;

    /* memory stick filename */
    s32 access = FILEACCESS_NONE;
    if (mode & O_RDONLY) access |= FILEACCESS_READ;
    if (mode & O_WRONLY) access |= FILEACCESS_WRITE;
    if (mode & O_APPEND) access |= FILEACCESS_APPEND;
    if (mode & O_CREAT)  access |= FILEACCESS_CREATE;

    h = mfs_openFile(filename, (FileAccess)access);
    if (h == 0) 
    {
        _log(ERR, HLE, "-1 = sceIoOpen(%s, %08x) - file not found", filename, mode);
        RETURN(SCE_ERROR_FILE_NOT_FOUND);
        mem_freeStr(filename);
        return;
    }

    f = malloc(sizeof(FileNode));
    id = ko_init(f, KO_FILENODE);
    f->handle = h;
    f->asyncResult = 0;
    _log(INF, HLE, "%d = sceIoOpen(%s, %08x)", id, filename, mode);
    RETURN(id);
    mem_freeStr(filename);
}

void sceIoOpenAsync()
{
    char *filename = mem_readStr(PARAM(0));
    s32 mode = PARAM(1);
    SceUID id;
    FileNode *f;
    u32 h;

    /* memory stick filename */
    s32 access = FILEACCESS_NONE;
    if (mode & O_RDONLY) access |= FILEACCESS_READ;
    if (mode & O_WRONLY) access |= FILEACCESS_WRITE;
    if (mode & O_APPEND) access |= FILEACCESS_APPEND;
    if (mode & O_CREAT)  access |= FILEACCESS_CREATE;

    h = mfs_openFile(filename, (FileAccess)access);
    if (h == 0) 
    {
        _log(ERR, HLE, "-1 = sceIoOpenAsync(%s, %08x) - file not found", filename, mode);
        RETURN(SCE_ERROR_FILE_NOT_FOUND);
        mem_freeStr(filename);
        return;
    }

    f = malloc(sizeof(FileNode));
    id = ko_init(f, KO_FILENODE);
    f->handle = h;
    f->asyncResult = id;
    _log(INF, HLE, "0 = sceIoOpenAsync(%s, %08x)", filename, mode);
    RETURN(id); /* TODO: verify how it actually works */
    mem_freeStr(filename);
}

void sceIoClose()  /* (s32 fd); */
{
    SceUID f = PARAM(0);
    if (ko_get(f))
    {
        _log(INF, HLE, "sceIoClose(fd = %d)", f);
        ko_free(f);
        RETURN(0);
    }
    else {
        _log(ERR, HLE, "sceIoClose(fd = %d): bad file descriptor", f);
        RETURN(SCE_ERROR_BAD_FILE_DESCRIPTOR);
    }
}

void sceIoRemove() /* (const char *file);  */
{
    _log(ERR, HLE, "sceIoRemove");
}

void sceIoMkdir()  /* (const char *dir, s32 mode);  */
{
    char *dir = mem_readStr(PARAM(0));
    _log(INF, HLE, "sceIoMkdir(%s, %o)", dir, PARAM(1));
    RETURN(mfs_mkdir(dir, PARAM(1)));
    mem_freeStr(dir);
}

void sceIoRmdir()  /* (const char *dir);  */
{
    _log(ERR, HLE, "sceIoRmdir");
}

void sceIoDevctl() /* (const char *name, s32 cmd, void *arg, size_t arglen, void *buf, size_t *buflen);  */
{
    char *device = mem_readStr(PARAM(0));
    s32 cmd = PARAM(1);
    u32 argAddr = PARAM(2);
    s32 argLen = PARAM(3);
    u32 bufAddr = PARAM(4);
    s32 bufLen = PARAM(5);
    _log(DBG, HLE, "sceIoDevctl(device = \"%s\", cmd = %08x, arg = %08x, arglen = %d, buf = %08x, buflen = %d)", 
                                  device, cmd, argAddr, argLen, bufAddr, bufLen);

    switch (cmd)
    {
    case 0x02015804: /* mscmhc0 callback register */
        _log(DBG, HLE, "-> mscmhc0 callback register");
        if (strcmp(device, "mscmhc0:") == 0)
        {
            if (mem_isOk(argAddr) && argLen >= 4)
            {
                __KernelRegisterCallback(CBTYPE_MS, mem_read32(argAddr));
                __KernelNotifyCallbackType(CBTYPE_MS, 1); /* memstick ready */
                RETURN(0);
            }
            else {
                _log(ERR, HLE, "-> Invalid argument!");
                RETURN(-1);
            }
        }
        else {
            _log(ERR, HLE, "-> Invalid device!");
            RETURN(SCE_ERROR_DEVCTL_BAD_PARAMS);
        }
        break;

    case 0x02025806: /* mscmhc0 device check */
        _log(DBG, HLE, "-> mscmhc0 device check");
        if (strcmp(device, "mscmhc0:") == 0)
        {
            if (mem_isOk(bufAddr)) {
                mem_write32(bufAddr, 1); /* memstick inserted */
                RETURN(0);
            }
            else {
                _log(ERR, HLE, "-> Invalid argument!");
                RETURN(-1);
            }
        }
        else {
            _log(ERR, HLE, "-> Invalid device!");
            RETURN(SCE_ERROR_UNSUPPORTED_OPERATION);
        }
        break;

    case 0x02415821: /* fatms0 callback register */
        _log(DBG, HLE, "-> fatms0 callback register");
        if (strcmp(device, "fatms0:") == 0)
        {
            if (mem_isOk(argAddr) && argLen >= 4)
            {
                __KernelRegisterCallback(CBTYPE_MS, mem_read32(argAddr));
                __KernelNotifyCallbackType(CBTYPE_MS, 1); /* memstick ready */
                RETURN(0);
            }
            else {
                _log(ERR, HLE, "-> Invalid argument!");
                RETURN(-1);
            }
        }
        else {
            _log(ERR, HLE, "-> Invalid device!");
            RETURN(SCE_ERROR_DEVCTL_BAD_PARAMS);
        }
        break;

    case 0x02425818: /* fatms0 capacity getting */
        _log(DBG, HLE, "-> fatms0 capacity getting");
        if (mem_isOk(argAddr) && argLen >= 4)
        {
            u32 addr = mem_read32(argAddr);
            if (mem_isOk(addr))
            {
                /* All values are fake */
                u32 sectorSize = 0x200;
                u32 sectorCount = 0x08;
                u32 freeSize = 1024 * 1024 * 1024; /* 1GB */
                u32 maxClusters = (float)(freeSize * 95L / 100) / (float)(sectorSize * sectorCount);
                u32 freeClusters = maxClusters;
                u32 maxSectors = 512;
                mem_write32(addr +  0, maxClusters);
                mem_write32(addr +  4, freeClusters);
                mem_write32(addr +  8, maxSectors);
                mem_write32(addr + 12, sectorSize);
                mem_write32(addr + 16, sectorCount);
                RETURN(0);
            }
            else {
                _log(ERR, HLE, "-> Invalid address contained at the argument address!");
                RETURN(-1);
            }
        }
        else {
            _log(ERR, HLE, "-> Invalid argument!");
            RETURN(-1);
        }
        break;

    case 0x02425823: /* fatms0 device check */
        _log(DBG, HLE, "-> fatms0 device check");
        if (strcmp(device, "fatms0:") == 0)
        {
            if (mem_isOk(bufAddr) && bufLen >= 4) {
                mem_write32(bufAddr, 1); /* memstick inserted */
                RETURN(0);
            }
            else {
                _log(ERR, HLE, "-> Invalid argument!");
                RETURN(-1);
            }
        }
        else {
            _log(ERR, HLE, "-> Invalid device!");
            RETURN(SCE_ERROR_DEVCTL_BAD_PARAMS);
        }
        break;

    default:
        _log(ERR, HLE, "Unrecognized devctl command 0x%08x", cmd);
        RETURN(-1);
        break;
    }

    mem_freeStr(device);
}

void sceIoRename() /* (const char *oldname, const char *newname);  */
{
    _log(ERR, HLE, "sceIoRename");
    RETURN(0);
}

void sceIoChdir()
{
    char *dir = mem_readStr(PARAM(0));
    mfs_chdir(dir);
    _log(INF, HLE, "sceIoChdir(%s)", dir);
    RETURN(1);
    mem_freeStr(dir);
}

typedef u32 (*DeferredAction)(SceUID);
DeferredAction defAction = 0;
u32 defParam;

void sceIoChangeAsyncPriority()
{
    _log(ERR, HLE, "UNIMPL sceIoChangeAsyncPriority(%d)", PARAM(0));
    RETURN(0);
}

void sceIoCloseAsync()
{
    _log(INF, HLE, "sceIoCloseAsync(%d)", PARAM(0));
    defAction = &__IoClose;
}

void sceIoLseekAsync()
{
    SceUID id = PARAM(0);
    FileNode *f = ko_get(id);
    if (f)
    {
        u64 offset = PARAM(2) | ((u64)PARAM(3) << 32);
        s32 whence = PARAM(4);
        FileMove seek;
        _log(INF, HLE, "sceIoLseekAsync(id = %d, offset = %016x, whence = %d)", id, offset, whence, PARAM(1));
        
        switch (whence)
        {
        case 0:
            seek = FILEMOVE_BEGIN;
            break;
        case 1:
            seek = FILEMOVE_CURRENT;
            break;
        case 2:
            seek = FILEMOVE_END;
            break;
        default:
            _log(ERR, HLE, "Unknown whence !");
            RETURN(-1);
            return;
        }

        f->asyncResult = mfs_seekFile(f->handle, offset, seek);
        RETURN(0);
    }
    else {
        _log(ERR, HLE, "sceIoLseekAsync(id = %d, offset = %016x, whence = %d): bad file descriptor", id, PARAM(2) | ((u64)PARAM(3) << 32), PARAM(4));
        RETURN(SCE_ERROR_BAD_FILE_DESCRIPTOR);
    }
}

void sceIoGetAsyncStat()
{
    SceUID id = PARAM(0);
    FileNode *f = ko_get(id);
    if (f)
    {
        mem_write32(PARAM(2), f->asyncResult & 0xffffffff);
        mem_write32(PARAM(2) + 4, f->asyncResult >> 32);
        _log(INF, HLE, "%d = sceIoGetAsyncStat(fd = %d, poll = %d, addr = %08x) (HACK)", mem_read32(PARAM(2)), id, PARAM(1), PARAM(2));
        RETURN(0); /* completed */
    }
    else {
        _log(ERR, HLE, "sceIoGetAsyncStat(fd = %d, poll = %d, addr = %08x): bad file descriptor", id, PARAM(1), PARAM(2));
        RETURN(SCE_ERROR_BAD_FILE_DESCRIPTOR);
    }
}


void sceIoWaitAsync()
{
    SceUID id = PARAM(0);
    FileNode *f = ko_get(id);
    if (f)
    {
        if (defAction)
        {
            u64 res = defAction(id);
            mem_write32(PARAM(1), res & 0xffffffff);
            mem_write32(PARAM(1) + 4, res >> 32);
            defAction = 0;
        }
        else {
            mem_write32(PARAM(1), f->asyncResult & 0xffffffff);
            mem_write32(PARAM(1) + 4, f->asyncResult >> 32);
        }
        _log(INF, HLE, "%d = sceIoWaitAsync(fd = %d, addr = %08x) (HACK)", mem_read32(PARAM(1)), id, PARAM(1));
        RETURN(0); /* completed */
    }
    else {
        _log(ERR, HLE, "sceIoWaitAsync(fd = %d, addr = %08x): bad file descriptor", id, PARAM(1));
        RETURN(SCE_ERROR_BAD_FILE_DESCRIPTOR);
    }
}

void sceIoWaitAsyncCB()
{
    SceUID id = PARAM(0);
    FileNode *f = ko_get(id);
    if (f)
    {
        if (defAction) {
            mem_write32(PARAM(1), defAction(id));
            defAction = 0;
        }
        else {
            mem_write32(PARAM(1), f->asyncResult & 0xffffffff);
            mem_write32(PARAM(1) + 4, f->asyncResult >> 32);
        }
        _log(INF, HLE, "0 = sceIoWaitAsyncCB(fd = %d, addr = %08x)", id, PARAM(1));
        RETURN(0); /* completed */
    }
    else {
        _log(ERR, HLE, "sceIoWaitAsyncCB(fd = %d, addr = %08x): bad file descriptor", id, PARAM(1));
        RETURN(SCE_ERROR_BAD_FILE_DESCRIPTOR);
    }
}

void sceIoPollAsync()
{
    SceUID id = PARAM(0);
    FileNode *f = ko_get(id);
    if (f)
    {
        if (defAction) {
            mem_write32(PARAM(1), defAction(id));
            defAction = 0;
        }
        else {
            mem_write32(PARAM(1), f->asyncResult & 0xffffffff);
            mem_write32(PARAM(1) + 4, f->asyncResult >> 32);
        }
        _log(INF, HLE, "0 = sceIoPollAsync(fd = %d, addr = %08x [%08x])", id, PARAM(1), mem_read32(PARAM(1)));
        RETURN(0);
    }
    else {
        _log(ERR, HLE, "sceIoPollAsync(fd = %d, addr = %08x): bad file descriptor", id, PARAM(1));
        RETURN(SCE_ERROR_BAD_FILE_DESCRIPTOR);
    }
}

void sceIoDopen() /* (const char *path);  */
{
    char *path = mem_readStr(PARAM(0));
    DirListing *dir;
    SceUID id;

    dir = malloc(sizeof(DirListing));
    id = ko_init(dir, KO_DIRLISTING);
    dir->nlisting = mfs_getDirListing(path, &dir->listing);
    dir->index = 0;
    dir->name = path;
    __IoAddDirHandle(id);

    _log(INF, HLE, "%d = sceIoDopen(\"%s\")", id, path);

    RETURN(id);
}

void sceIoDread()
{
    SceUID id = PARAM(0);
    DirListing *dir = ko_get(id);

    if (dir)
    {
        FileInfo info;
        SceIoDirEnt entry;
        if (dir->index == dir->nlisting)
        {
            _log(WRN, HLE, "0 = sceIoDread(fd = %d, addr = %08x)", PARAM(0), PARAM(1));
            RETURN(0);
            return;
        }
        else if (dir->nlisting == -1)
        {
            _log(ERR, HLE, "-1 = sceIoDread(fd = %d, addr = %08x)", PARAM(0), PARAM(1));
            RETURN(-1);
            return;
        }

        info = dir->listing[dir->index];

        mem_readStruct(PARAM(1), &entry, sizeof(SceIoDirEnt));

        __IoGetStat(&entry.d_stat, info);

        strncpy(entry.d_name, info.name, 32);
        entry.d_private = 0xc0debabe;
        _log(INF, HLE, "%d = sceIoDread(fd = %d, addr = %08x) = %s", dir->nlisting - dir->index, PARAM(0), PARAM(1), entry.d_name);

        mem_writeStruct(PARAM(1), &entry, sizeof(SceIoDirEnt));

        dir->index++;
        RETURN(dir->nlisting - dir->index);
    }
    else {
        _log(ERR, HLE, "sceIoDread(fd = %d, addr = %08x): bad file descriptor", id, PARAM(1));
        RETURN(SCE_ERROR_BAD_FILE_DESCRIPTOR);
    }
}

void sceIoDclose()
{
    u32 id = PARAM(0);
    DirListing *dir = ko_get(id);
    _log(INF, HLE, "sceIoDclose(fd = %d)", id);
    if (dir)
    {
        if (dir->nlisting != -1)
            mfs_freeDirListing(dir->name, dir->listing);
        mem_freeStr(dir->name);
        ko_free(id);
        RETURN(0);
        __IoDelDirHandle(id);
    }
    else {
        _log(ERR, HLE, "sceIoDclose(fd = %d): bad file descriptor", id);
        RETURN(SCE_ERROR_BAD_FILE_DESCRIPTOR);
    }
}

